#include "grflib_stdafx.h"
#include "../include/BillboardMesh.h"
#include "../include/D3D10Engine.h"


using namespace grflib::engine::core;
using namespace grflib::engine::aux;
using namespace grflib::engine;


CBillboardMesh::CBillboardMesh()
{
}

CBillboardMesh::~CBillboardMesh()
{
	OnDestroy();
}

HRESULT CBillboardMesh::PreRender(ID3D10Device *pd3dDevice,
    const D3DXMATRIX *pWorldMat,
    double fTime,
    float fElapsedTime,
    UINT pass)
{
	using namespace grflib::engine::core;
    HRESULT hr;

    bool bSucceeded = false;
    boost::shared_ptr<CEffect> pEffect;

    ////////////////////////////////////////
    //get technique
    if (pEffect = boost::dynamic_pointer_cast<CEffect>(_ResrcMan.FindObj(m_effectName)))
    {
        m_pEffect = pEffect;
        if (m_pTech = pEffect->GetTechnique(m_techniqueName))
        {
            V_RETURN( m_pTech->GetDesc(&m_techDesc) );
            bSucceeded = true;
        }
    }
    if (!bSucceeded)
    {
        W32ERR("Failed to switch to technique: %s/%s", m_effectName.c_str(), m_techniqueName.c_str());
        return E_FAIL;
    }

    ////////////////////////////////////////
    //get camera
    boost::shared_ptr<CCamera> &pCurCamera = _CamMan.GetCurrentCamera();
    if (!pCurCamera) //current camera not set
    {
        W32ERR(_T("Current camera not set!"));
        return E_FAIL;
    }

    ////////////////////////////////////////
    //set object independent shader variables
    ID3D10EffectMatrixVariable *pProjectMatVar = pEffect->GetShaderVariable("Projection")->AsMatrix();
    ID3D10EffectMatrixVariable *pViewMatVar = pEffect->GetShaderVariable("View")->AsMatrix();
    ID3D10EffectMatrixVariable *pWorldMatVar = pEffect->GetShaderVariable("World")->AsMatrix();
    if (!pProjectMatVar || !pViewMatVar || !pWorldMatVar) //uncompatible shader
    {
        W32ERR(_T("Shader uncompatible with static mesh!"));
        return E_FAIL;
    }

    D3DXMATRIX invViewMat;
    D3DXMatrixInverse(&invViewMat, 0, pCurCamera->GetDXUTCamera()->GetViewMatrix());
    invViewMat._41 = 0.0f; invViewMat._42 = 0.0f; invViewMat._43 = 0.0f;
    invViewMat = invViewMat * (*pWorldMat);

    V_RETURN( pProjectMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetProjMatrix() ) );
    V_RETURN( pViewMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetViewMatrix() ) );
    V_RETURN( pWorldMatVar->SetMatrix( (float*)&invViewMat ) );
    return S_OK;
}


///////////////////////////////////////////////////////////////////
// Axial billboard mesh
CAxialBillboardMesh::CAxialBillboardMesh()
    : m_axis(0.0f, 1.0f, 0.0f)
{
}

CAxialBillboardMesh::CAxialBillboardMesh(const float3 &axis)
    : m_axis(axis)
{
}

CAxialBillboardMesh::~CAxialBillboardMesh()
{
	OnDestroy();
}

HRESULT CAxialBillboardMesh::PreRender(ID3D10Device *pd3dDevice,
    const D3DXMATRIX *pWorldMat,
    double fTime,
    float fElapsedTime,
    UINT pass)
{
	using namespace grflib::engine::core;
    HRESULT hr;

    bool bSucceeded = false;
    boost::shared_ptr<CEffect> pEffect;

    ////////////////////////////////////////
    //get technique
    if (pEffect = boost::dynamic_pointer_cast<CEffect>(_ResrcMan.FindObj(m_effectName)))
    {
        m_pEffect = pEffect;
        if (m_pTech = pEffect->GetTechnique(m_techniqueName))
        {
            V_RETURN( m_pTech->GetDesc(&m_techDesc) );
            bSucceeded = true;
        }
    }
    if (!bSucceeded)
    {
        W32ERR("Failed to switch to technique: %s/%s", m_effectName.c_str(), m_techniqueName.c_str());
        return E_FAIL;
    }

    ////////////////////////////////////////
    //get camera
    boost::shared_ptr<CCamera> &pCurCamera = _CamMan.GetCurrentCamera();
    if (!pCurCamera) //current camera not set
    {
        W32ERR(_T("Current camera not set!"));
        return E_FAIL;
    }

    ////////////////////////////////////////
    //set object independent shader variables
    ID3D10EffectMatrixVariable *pProjectMatVar = pEffect->GetShaderVariable("Projection")->AsMatrix();
    ID3D10EffectMatrixVariable *pViewMatVar = pEffect->GetShaderVariable("View")->AsMatrix();
    ID3D10EffectMatrixVariable *pWorldMatVar = pEffect->GetShaderVariable("World")->AsMatrix();
    if (!pProjectMatVar || !pViewMatVar || !pWorldMatVar) //uncompatible shader
    {
        W32ERR(_T("Shader uncompatible with static mesh!"));
        return E_FAIL;
    }

    //Calculate billboardViewMat

    //first we calculate the coordination system based on m_axis
    D3DXVECTOR3 up(m_axis._x, m_axis._y, m_axis._z);
    D3DXVec3Normalize(&up, &up);
    D3DXVECTOR3 look;
    if ( abs(up.x) < 0.0001f && abs(up.y) < 0.0001f && 1.0f - abs(up.z) < 0.0001f )
    {
        //if axis is +/- z, set original look as -/+ y
        look.x = 0.0f;
        look.y = m_axis._z;
        look.z = 0.0f;
    }
    else
    {
        look.x = 0.0f;
        look.y = 0.0f;
        look.z = -1.0f;
    }
    D3DXVECTOR3 right;
    D3DXVec3Cross(&right, &up, &look);
    D3DXVec3Cross(&look, &right, &up);

    //project camInvLook onto the plane whose normal is m_axis
    //then calculate the rotation angle between InvProj and the original look on that plane
    //make the rotation matrix according to the angle and m_axis
    D3DXMATRIX invViewMat;
    D3DXMatrixInverse(&invViewMat, 0, pCurCamera->GetDXUTCamera()->GetViewMatrix());
    D3DXVECTOR3 camInvLook(invViewMat._31, invViewMat._32, invViewMat._33);

    float coeff = D3DXVec3Dot(&up, &camInvLook);
    float rotAng;
    if ( 1.0f - abs(coeff) < 0.0001f )
    {
        // when camInvLook is parallel with m_axis
        rotAng = 0.0f;
    }
    else
    {
        D3DXVECTOR3 invProj;
        invProj.x = camInvLook.x - up.x * coeff;
        invProj.y = camInvLook.y - up.y * coeff;
        invProj.z = camInvLook.z - up.z * coeff;
        D3DXVec3Normalize(&invProj, &invProj);
        float cosRotAng = D3DXVec3Dot(&invProj, &look);
        rotAng = acos(cosRotAng);

        if ( D3DXVec3Dot(&right, &invProj) < 0.0f )
        {
            rotAng = -rotAng;
        }
    }
    D3DXMatrixRotationAxis(&invViewMat, &up, rotAng);

    invViewMat = invViewMat * (*pWorldMat);

    V_RETURN( pProjectMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetProjMatrix() ) );
    V_RETURN( pViewMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetViewMatrix() ) );
    V_RETURN( pWorldMatVar->SetMatrix( (float*)&invViewMat ) );
    return S_OK;
}